'''
Created on Apr 5, 2013

@author: Yuan Zhao @Poly.edu

'''
import random, copy,sys,math,pygame
import ConfigParser
from ConnectFourGame import ConnectFourGameState,ConnectFourGame,alphabeta_search,herustic_search,random_move,EMPTY,RED,BLACK
from pygame.locals import *

BOARDWIDTH = 5  # how many spaces wide the board is
BOARDHEIGHT = 4 # how many spaces tall the board is
assert BOARDWIDTH >= 4 and BOARDHEIGHT >= 4, 'Board must be at least 4x4.'

# Difficulty level(1~8):when to do cut-off and do heuristic evaluation
EASY=1
NORMAL=3
HARD=7
DIFFICULTY = HARD
#UI parameters
SPACESIZE = 50 # size of the tokens and individual board spaces in pixels
FPS = 30 # frames per second to update the screen
WINDOWWIDTH = 640 # width of the program's window, in pixels
WINDOWHEIGHT = 480 # height in pixels

XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * SPACESIZE) / 2)
YMARGIN = int((WINDOWHEIGHT - BOARDHEIGHT * SPACESIZE) / 2)

PRUPLE = (90, 50, 100)
WHITE = (255, 255, 255)

BGCOLOR = PRUPLE
TEXTCOLOR = WHITE

#Program parameters
HUMAN = 'human'
COMPUTER = 'computer'


'''
Main Entry
'''
def main():
    global FPSCLOCK, DISPLAYSURF, REDPILERECT, BLACKPILERECT,DIFFICULTYRECT,game
    global BLACKTOKENIMG, REDTOKENIMG, BOARDIMG, ARROWIMG, ARROWRECT, HUMANWINNERIMG,DIFFICULTYIMG
    global COMPUTERWINNERIMG, WINNERRECT, TIEWINNERIMG
    
    #read config ini
    config = ConfigParser.ConfigParser()
    config.read('config.ini')
    diff_cfg = config.get('DEFAULT',"Difficulty")
    first_move_cfg = config.get("DEFAULT","FirstMove")
    
    DIFFICULTYIMG = None
    if diff_cfg == "easy":
        DIFFICULTY = EASY
        DIFFICULTYIMG = pygame.image.load('easy.png')
    elif diff_cfg == "normal":
        DIFFICULTY = NORMAL
        DIFFICULTYIMG = pygame.image.load('normal.png')
    else:
        DIFFICULTY = HARD
        DIFFICULTYIMG = pygame.image.load('hard.png')
     
    game = ConnectFourGame(BOARDHEIGHT,BOARDWIDTH,DIFFICULTY)
    
    print "Game Start:"
    print "Difficulty:",diff_cfg,'(',DIFFICULTY,')'
    print "Who move first:",first_move_cfg   
    #UI
    

    pygame.init()
    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    pygame.display.set_caption('Pop out Connect Four [Yuan Zhao 0488541@Poly.edu]')

    BLACKPILERECT = pygame.Rect(int(SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)
    REDPILERECT = pygame.Rect(WINDOWWIDTH - int(3 * SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)
    REDTOKENIMG = pygame.image.load('4row_red.png')
    REDTOKENIMG = pygame.transform.smoothscale(REDTOKENIMG, (SPACESIZE, SPACESIZE))
    BLACKTOKENIMG = pygame.image.load('4row_black.png')
    BLACKTOKENIMG = pygame.transform.smoothscale(BLACKTOKENIMG, (SPACESIZE, SPACESIZE))
    BOARDIMG = pygame.image.load('4row_board.png')
    BOARDIMG = pygame.transform.smoothscale(BOARDIMG, (SPACESIZE, SPACESIZE))

    HUMANWINNERIMG = pygame.image.load('win.png')
    COMPUTERWINNERIMG = pygame.image.load('lose.png')
    TIEWINNERIMG = pygame.image.load('tie.png')
    WINNERRECT = HUMANWINNERIMG.get_rect()
    WINNERRECT.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT-50))

    ARROWIMG = pygame.image.load('4row_arrow.png')
    ARROWRECT = ARROWIMG.get_rect()
    ARROWRECT.left = BLACKPILERECT.right + 10
    ARROWRECT.centery = BLACKPILERECT.centery
    DIFFICULTYRECT = pygame.Rect(0, 0, SPACESIZE, SPACESIZE)
    
    isFirstGame = True

    
    while True:
        runGame(isFirstGame,first_move_cfg)
        isFirstGame = False

'''
Game loop
'''
def runGame(isFirstGame,first_move_cfg):
    
    
    if isFirstGame:
        showHelp = True
    else:
        showHelp = False

    # choose who goes first.
    if first_move_cfg == "computer":
        turn = COMPUTER
    else:
        turn = HUMAN
    # Set up a blank board data structure.
    parentStateBoard = getNewBoard()
#    initialState = ConnectFourGameState()
#    initialState.board = parentStateBoard

    turn_count = 0
    while True: # main game loop
        turn_count+=1
        
        if turn == HUMAN:
            # Human player's turn.
            getHumanMove(parentStateBoard, showHelp)
            if showHelp:
                # turn off help arrow after the first move
                showHelp = False
            if isWinner(parentStateBoard, BLACK):
                gameResultImg = HUMANWINNERIMG
                break
            if isWinner(parentStateBoard, RED):
                gameResultImg = COMPUTERWINNERIMG
                break
            turn = COMPUTER # switch to other player's turn
        else:
            # Computer player's turn.
            (move,state) = getComputerMove(parentStateBoard,turn_count)
            #pop out a ball
            if move[0] == 'pop':
                animatePopingout(state.board,move[1],RED)            
            #drop a new ball
            else:
                animateComputerMoving(parentStateBoard, move[1])
                
            #update main board data
            parentStateBoard = state.board
                 
            if isWinner(state.board, RED):
                gameResultImg = COMPUTERWINNERIMG
                break
            if isWinner(state.board, BLACK):
                gameResultImg = HUMANWINNERIMG
                break
           
            # switch to other player's turn
            turn = HUMAN 

        if isBoardFull(parentStateBoard):
            # A completely filled board means it's a tie.
            gameResultImg = TIEWINNERIMG
            break

    #Show game result
    while True:
        # Keep looping until player clicks the mouse or quits.
        drawBoard(parentStateBoard)
        DISPLAYSURF.blit(gameResultImg, WINNERRECT)
        pygame.display.update()
        FPSCLOCK.tick()
        for event in pygame.event.get(): # event handling loop
            if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
                pygame.quit()
                sys.exit()
            elif event.type == MOUSEBUTTONDOWN:
                return


def makeMove(board, player, column):
    lowest = getLowestEmptySpace(board, column)
    if lowest != -1:
        board[column][lowest] = player


def drawBoard(board, extraToken=None):
    DISPLAYSURF.fill(BGCOLOR)
    
    #show difficulty
    DISPLAYSURF.blit(DIFFICULTYIMG, DIFFICULTYRECT)
    

    # draw tokens
    spaceRect = pygame.Rect(0, 0, SPACESIZE, SPACESIZE)
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
            if board[x][y] == RED:
                DISPLAYSURF.blit(REDTOKENIMG, spaceRect)
            elif board[x][y] == BLACK:
                DISPLAYSURF.blit(BLACKTOKENIMG, spaceRect)

    # draw the extra token
    if extraToken != None:
        if extraToken['color'] == RED:
            DISPLAYSURF.blit(REDTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE))
        elif extraToken['color'] == BLACK:
            DISPLAYSURF.blit(BLACKTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE))

    # draw board over the tokens
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
            DISPLAYSURF.blit(BOARDIMG, spaceRect)

    # draw the red and black tokens off to the side
    DISPLAYSURF.blit(REDTOKENIMG, REDPILERECT) # red on the left
    DISPLAYSURF.blit(BLACKTOKENIMG, BLACKPILERECT) # black on the right


def getNewBoard():
    board = []
    for x in range(BOARDWIDTH):
        board.append([EMPTY] * BOARDHEIGHT)
    return board


def getHumanMove(board, isFirstMove):
    draggingToken = False
    tokenx, tokeny = None, None
    while True:
        for event in pygame.event.get(): # event handling loop
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            #select a ball in the board
            elif event.type == MOUSEBUTTONDOWN and not BLACKPILERECT.collidepoint(event.pos) and not REDPILERECT.collidepoint(event.pos):
                x,y = event.pos
                rowNumber = (y - YMARGIN) / SPACESIZE
                colNumber = (x - XMARGIN) / SPACESIZE
                if rowNumber > BOARDHEIGHT - 1 or rowNumber < 0 or colNumber <0 or colNumber > BOARDWIDTH -1:
                    continue
                if board[colNumber][rowNumber] == BLACK and rowNumber == BOARDHEIGHT -1:#select a ball of one's own at the bottom row
                    #do the poping out
                    board[colNumber].pop()
                    board[colNumber].insert(0,None)
                    #animation
                    animatePopingout(board, colNumber, BLACK)
#                    drawBoard(board)
#                    pygame.display.update()
                    return
                    
            elif event.type == MOUSEBUTTONDOWN and not draggingToken and BLACKPILERECT.collidepoint(event.pos):
                # start of dragging on red token pile.
                draggingToken = True
                tokenx, tokeny = event.pos
            elif event.type == MOUSEMOTION and draggingToken:
                # update the position of the red token being dragged
                tokenx, tokeny = event.pos
            elif event.type == MOUSEBUTTONUP and draggingToken:
                # let go of the token being dragged
                if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN:
                    # let go at the top of the screen.
                    column = int((tokenx - XMARGIN) / SPACESIZE)
                    if isValidMove(board, column):
                        animateDroppingToken(board, column, BLACK)
                        board[column][getLowestEmptySpace(board, column)] = BLACK
                        drawBoard(board)
                        pygame.display.update()
                        return
                tokenx, tokeny = None, None
                draggingToken = False
        if tokenx != None and tokeny != None:
            drawBoard(board, {'x':tokenx - int(SPACESIZE / 2), 'y':tokeny - int(SPACESIZE / 2), 'color':BLACK})
        else:
            drawBoard(board)

        if isFirstMove:
            # Show the help arrow for the player's first move.
            DISPLAYSURF.blit(ARROWIMG, ARROWRECT)

        pygame.display.update()
        FPSCLOCK.tick()


def animateDroppingToken(board, column, color):
    x = XMARGIN + column * SPACESIZE
    y = YMARGIN - SPACESIZE
    dropSpeed = 1.0

    lowestEmptySpace = getLowestEmptySpace(board, column)

    while True:
        y += int(dropSpeed)
        dropSpeed += 0.5
        if int((y - YMARGIN) / SPACESIZE) >= lowestEmptySpace:
            return
        drawBoard(board, {'x':x, 'y':y, 'color':color})
        pygame.display.update()
        FPSCLOCK.tick()

def animatePopingout(board,column,tile):
    x = XMARGIN + column * SPACESIZE
    y = YMARGIN + BOARDHEIGHT*SPACESIZE
    
    speed = 1.0
    while y < WINDOWHEIGHT - int(3 * SPACESIZE / 2):
            y += int(speed)
            speed += 0.5
            drawBoard(board, {'x':x, 'y':y, 'color':tile})
            pygame.display.update()
            FPSCLOCK.tick()
            
def animateComputerMoving(board, move):
        x = REDPILERECT.left
        y = REDPILERECT.top
        speed = 1.0
        # moving the red tile up--vertically
        while y > (YMARGIN - SPACESIZE):
            y -= int(speed)
            speed += 0.5
            drawBoard(board, {'x':x, 'y':y, 'color':RED})
            pygame.display.update()
            FPSCLOCK.tick()
        # moving the red tile over--horizontally
        y = YMARGIN - SPACESIZE
        speed = 1.0
        while x > (XMARGIN + move * SPACESIZE):
            x -= int(speed)
            speed += 0.5
            drawBoard(board, {'x':x, 'y':y, 'color':RED})
            pygame.display.update()
            FPSCLOCK.tick()
        # dropping the red tile
        animateDroppingToken(board, move, RED)


def getComputerMove(board,turn_count):
    parentState = ConnectFourGameState()
    parentState.board = board
    parentState.to_move = BLACK
    (move,state) = (0,0)
    if turn_count<=1:#takes random moves in the first turn
        (move,state) = random_move(parentState,game)
    else:#use alpha-beta pruning search afterwards
        (move,state) = alphabeta_search(parentState, game)
        
    return (move,state)

def getLowestEmptySpace(board, column):
    # Return the row number of the lowest empty row in the given column.
    for y in range(BOARDHEIGHT-1, -1, -1):
        if board[column][y] == EMPTY:
            return y
    return -1


def isValidMove(board, column):
    # Returns True if there is an empty space in the given column.
    # Otherwise returns False.
    if column < 0 or column >= (BOARDWIDTH) or board[column][0] != EMPTY:
        return False
    return True


def isBoardFull(board):
    # Returns True if there are no empty spaces anywhere on the board.
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            if board[x][y] == EMPTY:
                return False
    return True


def isWinner(board, tile):
    # check horizontal spaces
    for x in range(BOARDWIDTH - 3):
        for y in range(BOARDHEIGHT):
            if board[x][y] == tile and board[x+1][y] == tile and board[x+2][y] == tile and board[x+3][y] == tile:
                return True
    # check vertical spaces
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT - 3):
            if board[x][y] == tile and board[x][y+1] == tile and board[x][y+2] == tile and board[x][y+3] == tile:
                return True
    # check / diagonal spaces
    for x in range(BOARDWIDTH - 3):
        for y in range(3, BOARDHEIGHT):
            if board[x][y] == tile and board[x+1][y-1] == tile and board[x+2][y-2] == tile and board[x+3][y-3] == tile:
                return True
    # check \ diagonal spaces
    for x in range(BOARDWIDTH - 3):
        for y in range(BOARDHEIGHT - 3):
            if board[x][y] == tile and board[x+1][y+1] == tile and board[x+2][y+2] == tile and board[x+3][y+3] == tile:
                return True
    return False


if __name__ == '__main__':
    main()

